package ru.nstu.isma.core.lang;

import ru.nstu.isma.core.hsm.*;
import ru.nstu.isma.core.hsm.exp.EXPOperand;
import ru.nstu.isma.core.hsm.exp.EXPOperator;
import ru.nstu.isma.core.hsm.exp.EXPToken;

import java.util.HashSet;
import java.util.List;
import java.util.Stack;

/**
 * by
 * Bessonov Alex.
 * Date: 14.11.13 Time: 0:21
 */
public class ConstValueCalculator {

    public static Double calculate(HMConst c) {
        if (isCalculated(c)) {
            return c.getValue();
        }
        List<EXPToken> tokens = c.getRightPart().getTokens();
        validateExpression(tokens);
        avoidLoopCheck(c, null);
        Double value = runCaltulator(tokens);
        c.setValue(value);
        return value;
    }

    private static void validateExpression(List<EXPToken> tokens) {
        // проверяем что все токены - константы
        for (EXPToken t : tokens) {
            if (t instanceof EXPOperand) {
                HMVariable var = ((EXPOperand) t).getVariable();
                if (!(var instanceof HMConst)) {
                    // TODO обработка ошибки (семантика)
                }
            }
        }
    }

    private static void avoidLoopCheck(HMConst c, HashSet<String> prevCheck) {

        if (c instanceof HMUnnamedConst) {
            return;
        }
        HashSet<String> current = new HashSet<String>();
        if (prevCheck != null) {
            for (String s : prevCheck) {
                current.add(s);
            }
        }
        List<EXPToken> tokens = c.getRightPart().getTokens();
        for (EXPToken token : tokens) {
           if (token instanceof EXPOperand) {
               HMConst var = (HMConst) ((EXPOperand) token).getVariable();
               if (current.contains(var.getCode())) {
                   // TODO получили петлю -  семантика обработка ошибки
               } else {
                   avoidLoopCheck(var, current);
               }
           }
        }
    }

    private static Double runCaltulator(List<EXPToken> tokens) {
        Stack<Double> stack = new Stack<Double>();
        for (EXPToken token : tokens) {
            if (token instanceof EXPOperand) {
                HMVariable var = ((EXPOperand) token).getVariable();
                stack.push(calculate((HMConst)var));
            } else if (token instanceof EXPOperator) {
                doOperation(stack, (EXPOperator) token);
            }
        }
        return stack.pop();
    }

    private static void doOperation(Stack<Double> values, EXPOperator o) {
        double result = 0;
        if (o.getType() != EXPOperator.Type.ALGEBRAIC ) {
            // TODO семантическая ошибка
        }
        if (o.getArity() == EXPOperator.ArityType.BINARY) {
            double v1 = values.pop();
            double v2 = values.pop();
            EXPOperator.Code c =  o.getCode();
            switch (c) {
                case ADDITION:
                    result = v1 + v2;
                    break;
                case DIVISION:
                    result = v2 / v1;
                    break;
                case SUBTRACTION:
                    result = v2 - v1;
                    break;
                case MULTIPLICATION:
                    result = v1 * v2;
                    break;
                default:
                    // TODO неизвестный операнд
                    break;
            }

        } else if (o.getArity() == EXPOperator.ArityType.UNARY) {
            double v1 = values.pop();
            EXPOperator.Code c =  o.getCode();
            switch (c) {
                case ADDITION:
                    result = +v1;
                case SUBTRACTION:
                    result = -v1;
                    break;
            }
        }
        values.push(result);
    }

    private static boolean isCalculated(HMConst c) {
        return c.getValue() != null;
    }

}
